package httpfs

import (
	"fmt"
	"net/http"
	"net/url"
	"os"
	"path"
	"sort"
	"strings"
)

type servFileRequest struct {
	w         http.ResponseWriter
	r         *http.Request
	fs        http.FileSystem
	name      string
	redirect  bool
	listtype  int
	listFiles bool
}

// name is '/'-separated, not filepath.Separator.
func serveFileEx(req *servFileRequest) {
	const indexPage = "/index.html"

	// redirect .../index.html to .../
	// can't use Redirect() because that would make the path absolute,
	// which would be a problem running under StripPrefix
	if strings.HasSuffix(req.r.URL.Path, indexPage) {
		localRedirect(req.w, req.r, "./")
		return
	}

	if req.fs == nil {
		http.Error(req.w, "404 fs page not found", http.StatusNotFound)
		return
	}

	f, err := req.fs.Open(req.name)
	if err != nil {
		msg, code := toHTTPError(err)
		http.Error(req.w, msg, code)
		return
	}
	defer f.Close()

	d, err := f.Stat()
	if err != nil {
		msg, code := toHTTPError(err)
		http.Error(req.w, msg, code)
		return
	}

	if req.redirect {
		// redirect to canonical path: / at end of directory url
		// r.URL.Path always begins with /
		url := req.r.URL.Path
		if d.IsDir() {
			if url[len(url)-1] != '/' {
				localRedirect(req.w, req.r, path.Base(url)+"/")
				return
			}
		} else {
			if url[len(url)-1] == '/' {
				localRedirect(req.w, req.r, "../"+path.Base(url))
				return
			}
		}
	}

	// redirect if the directory name doesn't end in a slash
	if d.IsDir() {
		url := req.r.URL.Path
		if url[len(url)-1] != '/' {
			localRedirect(req.w, req.r, path.Base(url)+"/")
			return
		}
	}

	// use contents of index.html for directory, if present
	if d.IsDir() {
		index := strings.TrimSuffix(req.name, "/") + indexPage
		ff, err := req.fs.Open(index)
		if err == nil {
			defer ff.Close()
			dd, err := ff.Stat()
			if err == nil {
				req.name = index
				d = dd
				f = ff
			}
		}
	}

	// Still a directory? (we didn't find an index.html file)
	if d.IsDir() {
		if checkIfModifiedSince(req.r, d.ModTime()) == condFalse {
			writeNotModified(req.w)
			return
		}
		if req.listFiles {
			if req.listtype == 0 {
				req.w.Header().Set("Last-Modified", d.ModTime().UTC().Format(http.TimeFormat))
				dirList(req.w, req.r, req.name, f)
			} else {
				dirListJSON(req.w, req.r, req.name, f)
			}
		} else {
			http.Error(req.w, "404 not found", 404)
		}
		return
	}

	// serveContent will check modification time
	sizeFunc := func() (int64, error) { return d.Size(), nil }
	serveContent(req.w, req.r, d.Name(), d.ModTime(), sizeFunc, f)
}

func OnHttpDownFile(w http.ResponseWriter, r *http.Request, filename string) {
	f, err := os.OpenFile(filename, os.O_RDONLY, 0666)
	if err != nil {
		msg, code := toHTTPError(err)
		http.Error(w, msg, code)
		return
	}
	defer f.Close()

	d, err := f.Stat()
	if err != nil {
		msg, code := toHTTPError(err)
		http.Error(w, msg, code)
		return
	}
	sizeFunc := func() (int64, error) { return d.Size(), nil }
	serveContent(w, r, d.Name(), d.ModTime(), sizeFunc, f)
}

func dirListJSON(w http.ResponseWriter, r *http.Request, dirname string, f http.File) {
	dirs, err := f.Readdir(-1)
	if err != nil {
		if len(dirs) == 0 {
			logf(r, "http: error reading directory: %v", err)
			var sb strings.Builder
			sb.WriteString("[")
			errstr := err.Error()
			if JSONEscapeFunc != nil {
				errstr = JSONEscapeFunc(errstr)
			}
			itm := fmt.Sprintf("{\"type\":-1, \"name\":\"%s\"}", errstr)
			sb.WriteString(itm)
			sb.WriteString("]")
			http.Error(w, "Error reading directory", http.StatusInternalServerError)
			return
		}
	}
	// 目录拍前面
	sort.Slice(dirs, func(i, j int) bool {
		if dirs[i].IsDir() == dirs[j].IsDir() {
			return dirs[i].Name() < dirs[j].Name()
		} else {
			return dirs[i].IsDir()
		}
	})
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	var sb strings.Builder
	sb.WriteString("[")
	if len(dirname) > 0 && dirname != "/" {
		url := url.URL{Path: "../"}
		itm := fmt.Sprintf("{\"url\":\"%s\", \"type\":0, \"name\":\"%s\"}", url.String(), "..")
		sb.WriteString(itm)
	}

	for _, d := range dirs {
		name := d.Name()

		if d.IsDir() {
			name += "/"
			url := url.URL{Path: name}
			if sb.Len() > 1 {
				sb.WriteString(",")
			}
			sb.WriteString("{\"url\":\"")
			sb.WriteString(url.String())
			sb.WriteString("\",\"type\":0, \"name\":\"")
			sb.WriteString(htmlReplacer.Replace(name))
			sb.WriteString("\"}")
		} else {
			url := url.URL{Path: name}
			if sb.Len() > 1 {
				sb.WriteString(",")
			}
			sb.WriteString("{\"url\":\"")
			sb.WriteString(url.String())
			sb.WriteString("\",\"type\":1, \"name\":\"")
			sb.WriteString(htmlReplacer.Replace(name))
			sb.WriteString("\",")
			itmstr := fmt.Sprintf("\"szie\":%d, \"modtime\":\"%s\"", d.Size(), DateTimeString(d.ModTime()))
			sb.WriteString(itmstr)
			sb.WriteString("}")
		}
	}
	sb.WriteString("]")
	//fmt.Println(sb.String())
	fmt.Fprint(w, sb.String())
}

func dirList(w http.ResponseWriter, r *http.Request, dirname string, f http.File) {
	dirs, err := f.Readdir(-1)
	if err != nil {
		if len(dirs) == 0 {
			logf(r, "http: error reading directory: %v", err)
			http.Error(w, "Error reading directory", http.StatusInternalServerError)
			return
		}
	}
	// 目录拍前面
	sort.Slice(dirs, func(i, j int) bool {
		if dirs[i].IsDir() == dirs[j].IsDir() {
			return dirs[i].Name() < dirs[j].Name()
		} else {
			return dirs[i].IsDir()
		}
	})

	w.Header().Del("Last-Modified")
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	w.Header().Set("Cache-Control", "no-cache;no-store")
	fmt.Fprint(w, "<head>")
	fmt.Fprint(w, "<meta http-equiv=\"pragma\" content=\"no-cache\">\n")
	fmt.Fprint(w, "<meta http-equiv=\"cache-control\" content=\"no-cache\">\n")
	fmt.Fprint(w, "<meta http-equiv=\"expires\" content=\"0\">\n")
	fmt.Fprint(w, "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no\">\n")
	fmt.Fprint(w, "<style>.fs-row {width: 100%}\n.fs-col-xs20 {display: inline-block; width: 20%}\n.fs-col-xs50 {display: inline-block; width: 50%}\n</style>\n")
	fmt.Fprint(w, "</head>")
	fmt.Fprintf(w, "<body><pre>\n")
	var fsize string
	var ftime string
	var fcolfile string
	if len(dirname) > 0 && dirname != "/" {
		// name may contain '?' or '#', which must be escaped to remain
		// part of the URL path, and not indicate the start of a query
		// string or fragment.
		url := url.URL{Path: "../"}

		//fcolfile = fmt.Sprintf("<span class=\"fs-col-xs50 fs-lable-file\"><a href=\"%s\">%s</a></span>", url.StatusString(), htmlReplacer.Replace(name))
		fcolfile = fmt.Sprintf("<div class=\"fs-col-xs50 fs-lable-file\"><a href=\"%s\">%s</a></div>", url.String(), "..")
		className := "class=\"fs-row fs-dirrow\""
		fmt.Fprintf(w, "<div %s>%s</div>", className, fcolfile)
	}

	for _, d := range dirs {
		name := d.Name()
		className := "class=\"fs-row fs-filerow\""

		if d.IsDir() {
			name += "/"
			className = "class=\"fs-row fs-dirrow\""
			fsize = ""
			ftime = ""
		} else {
			fsize = fmt.Sprintf("<div class=\"fs-col-xs20 fs-lable-size\">%s</div>", HumanFilesize(d.Size()))
			ftime = fmt.Sprintf("<div class=\"fs-col-xs20 fs-lable-modtime\">%s</div>", DateTimeString(d.ModTime()))
		}
		// name may contain '?' or '#', which must be escaped to remain
		// part of the URL path, and not indicate the start of a query
		// string or fragment.
		url := url.URL{Path: name}

		//fcolfile = fmt.Sprintf("<span class=\"fs-col-xs50 fs-lable-file\"><a href=\"%s\">%s</a></span>", url.StatusString(), htmlReplacer.Replace(name))
		fcolfile = fmt.Sprintf("<div class=\"fs-col-xs50 fs-lable-file\"><a href=\"%s\">%s</a></div>", url.String(), htmlReplacer.Replace(name))

		fmt.Fprintf(w, "<div %s>%s %s %s</div>", className, fcolfile, fsize, ftime)
	}
	fmt.Fprintf(w, "</pre></body>\n")
}
